// SimpleProgram.cpp
//
// A simple 2D OpenGL program

#define GL3_PROTOTYPES

// Include the vector and matrix utilities from the textbook, as well as some
// macro definitions.
#include "Angel.h"

#ifdef __APPLE__
#  include <OpenGL/gl3.h>
#endif

#include <stdio.h>
#include <fstream>
#include <iostream>
#include <string>
#include <boost/algorithm/string.hpp>
#include <vector>

// A global constant for the number of points that will be in our object.
const int NumPoints = 21600 * 6;
using namespace std;
using namespace boost;

//-----------------------------------------------------------------------
// Reads those gosh darned .txt files
// They are #very#emotional
vector< vector<float> > readDataSet(string dataSetText){
        int m;
        int n;

        string line;
        vector <string> data;
        vector <string> fields;
        vector < vector <float> > dataMatrix;
        ifstream dataFile(dataSetText.c_str());

        if(dataFile.is_open())
        {
                getline(dataFile,line);
                split(fields,line,is_any_of(" \n"));
        }

        m = atoi(fields[1].c_str());
        n = atoi(fields[2].c_str());

        while(!dataFile.eof())
        {
                getline(dataFile,line);
                split(fields,line,is_any_of(" "));
                data.insert(data.end(),fields.begin(),fields.end()-1);
        }

        dataMatrix.resize(n,vector<float>(m));
        for(int i = 0; i < n; i++)
        {
                for(int j = 0; j < m; j++)
                {
                dataMatrix[i][j] = atof(data[m*i+j].c_str());
                }
        }
        return dataMatrix;
}
//-------------------------------------------------------------------------
// Does something /discretely/
vector <vector <int> > discretizeData(vector <vector <float> > data, int numBuckets){
        //accept data matrix
        //do that crazy function over every value
        //return matrix of ints
        float tempLargest = 0;
        float tempSmallest = 0;
        float temp = 0;

        for(size_t i = 0; i < data.size();i++)
        {
                temp =  *max_element(data[i].begin(),data[i].end());
                if(temp != 0 && temp > tempLargest)
                        tempLargest = temp;

                temp =  *min_element(data[i].begin(),data[i].end());
                if(temp != 0 && temp < tempSmallest)
                        tempSmallest = temp;
        }

        vector < vector<int> > bucketMatrix;
        bucketMatrix.resize(data.size(),vector<int>(data[0].size()));
        for(int i = 0; i < bucketMatrix.size(); i++)
        {
                for(int j = 0; j < bucketMatrix[0].size(); j++)
                {
                temp = data[i][j];
                temp == 0 ? bucketMatrix[i][j] = -2 : bucketMatrix[i][j] = (int)((temp-tempSmallest)/(tempLargest-tempSmallest) * (numBuckets - 1) + .5) + 1;
                }
        }
        int * flippedBuckets = new int[numBuckets];
        int bucketCount = numBuckets;

        for(int i=1; i<numBuckets+1; i++)
        {
            flippedBuckets[--bucketCount] = i;
        }

        for(int i = 0; i < bucketMatrix.size(); i++)
        {
            for(int j=0; j < bucketMatrix[0].size(); j++)
            {
                if(bucketMatrix[i][j] != -2){
                    bucketMatrix[i][j] = flippedBuckets[bucketMatrix[i][j]];
                }
            }
        }
        return bucketMatrix;
}
//----------------------------------------------------------------------------
//Transpose data
vector< vector< float> > transposeData(vector<vector<float> > data){
    vector<vector<float> > transposed(data[0].size(),vector<float>(data.size()));

    for (size_t i = 0; i < data.size(); ++i)
    {
        for(size_t j=0; j < data[0].size(); ++j)
        {
            transposed[j][i] = data[i][j];
        }
    }
    return transposed;
}

//----------------------------------------------------------------------------
// the HSV color model will be as follows
// h : [0 - 360]
// s : [0 - 1]
// v : [0 - 1]
// If you want it differently (in a 2 * pi scale, 256 instead of 1, etc,
// you'll have to change it yourself.
// rgb is returned in 0-1 scale (ready for color3f)
void HSVtoRGB(float hsv[3], float rgb[3]) {
	float tmp1 = hsv[2] * (1-hsv[1]);
	float tmp2 = hsv[2] * (1-hsv[1] * (hsv[0] / 60.0f - (int) (hsv[0]/60.0f) ));
	float tmp3 = hsv[2] * (1-hsv[1] * (1 - (hsv[0] / 60.0f - (int) (hsv[0]/60.0f) )));
	switch((int)(hsv[0] / 60)) {
		case 0:
			rgb[0] = hsv[2] ;
			rgb[1] = tmp3 ;
			rgb[2] = tmp1 ;
			break;
		case 1:
			rgb[0] = tmp2 ;
			rgb[1] = hsv[2] ;
			rgb[2] = tmp1 ;
			break;
		case 2:
			rgb[0] = tmp1 ;
			rgb[1] = hsv[2] ;
			rgb[2] = tmp3 ;
			break;
		case 3:
			rgb[0] = tmp1 ;
			rgb[1] = tmp2 ;
			rgb[2] = hsv[2] ;
			break;
		case 4:
			rgb[0] = tmp3 ;
			rgb[1] = tmp1 ;
			rgb[2] = hsv[2] ;
			break;
		case 5:
			rgb[0] = hsv[2] ;
			rgb[1] = tmp1 ;
			rgb[2] = tmp2 ;
			break;
		default:
			cout << "Inconceivable!\n";

	}

}
//----------------------------------------------------------------------------
// More discretion is required
vec3 *  discreteToRGB(vector< vector<int> > data,int numCols, int numRows){
        float val = 1.0; //val is mal
        float sat = 1.0; //With a SAT score this low I'll never get into MIT...
        float hsv[3];
        float rgb[3];
        int scale = 12;
        vec3 *coolers = new vec3[NumPoints];
        vector <float> colors;

        int count = 0;

        for(int i = 0; i < numRows; i++)
        {
                for(int j = 0; j  < numCols; j++)
                {
                        if(data[i][j] != -2){
                                hsv[0] = (float)(scale*data[i][j]);
                                hsv[1] = sat;
                                hsv[2] = val;
                                HSVtoRGB(hsv,rgb);
                        }else{
                                rgb[0] = 1.0;
                                rgb[1] = 1.0;
                                rgb[2] = 1.0;
                        }
                            coolers[count++] = vec3(rgb[0],rgb[1],rgb[2]);
                            coolers[count++] = vec3(rgb[0],rgb[1],rgb[2]);
                            coolers[count++] = vec3(rgb[0],rgb[1],rgb[2]);
                            coolers[count++] = vec3(rgb[0],rgb[1],rgb[2]);
                            coolers[count++] = vec3(rgb[0],rgb[1],rgb[2]);
                            coolers[count++] = vec3(rgb[0],rgb[1],rgb[2]);
                            //cout << "not broke\n";
                }
        }
        return coolers;
}
//----------------------------------------------------------------------------
// VERTS VERTS IT HURTS
vec2 * makeVertices(int numCols, int numRows){
    vec2 *vertices = new vec2[numRows*numCols*6];
    int vecCounter=0;
    float temp1;
    float temp2;

    for(int i=0; i<numRows; i++){
        for(int j=0; j<numCols; j++){
            temp1=(float)i;
            temp2=(float)j;

            vertices[vecCounter++]=vec2((2*(temp1)/numRows)-1,(2*(temp2+1)/numCols)-1);
            vertices[vecCounter++]=vec2((2*(temp1)/numRows)-1,(2*(temp2)/numCols)-1);
            vertices[vecCounter++]=vec2((2*(temp1+1)/numRows)-1,(2*(temp2+1)/numCols)-1);
            vertices[vecCounter++]=vec2((2*(temp1)/numRows)-1,(2*(temp2)/numCols)-1);
            vertices[vecCounter++]=vec2((2*(temp1+1)/numRows)-1,(2*(temp2)/numCols)-1);
            vertices[vecCounter++]=vec2((2*(temp1+1)/numRows)-1,(2*(temp2+1)/numCols)-1);

        }
    }


    return vertices;
}
//----------------------------------------------------------------------------
vector<vec2> makeContourVertices(vector<vector<int> > discretedData){
    vector <vec2> contourVertices;
    float temp1;
    float temp2;
    int numRows = discretedData.size();
    int numCols = discretedData[0].size();

    for (int i= 0; i < numRows; i++) {
        for (int j = 0; j < numCols; j++) {
            temp1 = (float)i;
            temp2 = (float)j;
            if(i == 0 && discretedData[i][j] != -2 )
            {
                contourVertices.push_back(vec2((2*(temp1)/numRows)-1,(2*(temp2)/numCols)-1));
                contourVertices.push_back(vec2((2*(temp1)/numRows)-1,(2*(temp2+1)/numCols)-1));
            }
            if(i == numRows-1 && discretedData[i][j] != -2)
            {
                contourVertices.push_back(vec2((2*(temp1+1)/numRows)-1,(2*(temp2)/numCols)-1));
                contourVertices.push_back(vec2((2*(temp1+1)/numRows)-1,(2*(temp2+1)/numCols)-1));
            }
            if(j == 0 && discretedData[i][j] != -2 )
            {
                contourVertices.push_back(vec2((2*(temp1)/numRows)-1,(2*(temp2)/numCols)-1));
                contourVertices.push_back(vec2((2*(temp1+1)/numRows)-1,(2*(temp2)/numCols)-1));
            }
            if(j == numCols-1 && discretedData[i][j] != -2)
            {
                contourVertices.push_back(vec2((2*(temp1)/numRows)-1,(2*(temp2+1)/numCols)-1));
                contourVertices.push_back(vec2((2*(temp1+1)/numRows)-1,(2*(temp2+1)/numCols)-1));
            }

            if (i+1 < numRows){
                if (discretedData[i][j] != discretedData[i+1][j])
                {
                    contourVertices.push_back(vec2((2*(temp1+1)/numRows)-1,(2*(temp2+1)/numCols)-1));
                    contourVertices.push_back(vec2((2*(temp1+1)/numRows)-1,(2*(temp2)/numCols)-1));
                }
            }
            if (j-1 > 0){
                if (discretedData[i][j] != discretedData[i][j+1])
                {
                    contourVertices.push_back(vec2((2*(temp1+1)/numRows)-1,(2*(temp2+1)/numCols)-1));
                    contourVertices.push_back(vec2((2*(temp1)/numRows)-1,(2*(temp2+1)/numCols)-1));
                }
            }
        }
    }
    return contourVertices;
}

//----------------------------------------------------------------------------
vec3 * makeContourColors(int num){
    vec3 * colors = new vec3[num];
    for(int i = 0; i < num; i++)
    {
        colors[i] = vec3(0,0,0);
    }
    return colors;
}

// Create a vertex array object---OpenGL needs this to manage the Vertex
// Buffer Object
GLuint vao[2];
int numLines;

//----------------------------------------------------------------------------
void init(vec2 * vertices,vec3 * colors,vec2 * contourV, vec3 * contourC)
{
    // Generate the vertex array and then bind it to make make it active.
    glGenVertexArrays(2, vao);
    glBindVertexArray(vao[0]);

    // Create and initialize a buffer object---that's the memory buffer that
    // will be on the card!
    GLuint buffer[2];

    // We only need one for this example.
    glGenBuffers(2, buffer);

    // Bind makes it the active VBO
    glBindBuffer(GL_ARRAY_BUFFER, buffer[0]);

    // Here we copy the vertex data into our buffer on the card.  The parameters
    // tell it the type of buffer object, the size of the data in bytes, the
    // pointer for the data itself, and a hint for how we intend to use it.
    glBufferData(GL_ARRAY_BUFFER, NumPoints * sizeof(vec2), vertices, GL_STATIC_DRAW);

    // Load the shaders.  Note that this function is not offered by OpenGL
    // directly, but provided as a convenience.
    GLuint program = InitShader("vshader32.glsl", "fshader32.glsl");

    // Make that shader program active.
    glUseProgram(program);

    // Initialize the vertex position attribute from the vertex shader.  When
    // the shader and the program are linked, a table is created for the shader
    // variables.  Here, we get the index of the vPosition variable.
    GLuint loc = glGetAttribLocation(program, "vPosition");

    // We want to set this with an array!
    glEnableVertexAttribArray(loc);

    // We map it to this offset in our current buffer (VBO) So, our position
    // data is going into loc and contains 2 floats.  The parameters to this
    // function are the index for the shader variable, the number of components,
    // the type of the data, a boolean for whether or not this data is
    // normalized (0--1), stride (or byte offset between consective attributes),
    // and a pointer to the first component.  Note that BUFFER_OFFSET is a macro
    // defined in the Angel.h file.
    glVertexAttribPointer(loc, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));

    // Now repeat lots of that stuff for the colors
    glBindBuffer(GL_ARRAY_BUFFER, buffer[1]);
    glBufferData(GL_ARRAY_BUFFER, NumPoints * sizeof(vec3), colors, GL_STATIC_DRAW);

    GLuint colorLoc = glGetAttribLocation(program, "vColor");
    glEnableVertexAttribArray(colorLoc);
    glVertexAttribPointer(colorLoc, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));

    //Contour vao
    glBindVertexArray(vao[1]);
    GLuint buffer2[2];

    //Contour vertices vbo
    glGenBuffers(2, buffer2);
    glBindBuffer(GL_ARRAY_BUFFER, buffer2[0]);
    glBufferData(GL_ARRAY_BUFFER, numLines * sizeof(vec2), contourV, GL_STATIC_DRAW);
    GLuint loc2 = glGetAttribLocation(program, "vPosition");
    glEnableVertexAttribArray(loc2);
    glVertexAttribPointer(loc2, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));

    //Contour color vbo
    glBindBuffer(GL_ARRAY_BUFFER, buffer2[1]);
    glBufferData(GL_ARRAY_BUFFER, numLines * sizeof(vec3), contourC, GL_STATIC_DRAW);
    GLuint colorLoc2 = glGetAttribLocation(program, "vColor");
    glEnableVertexAttribArray(colorLoc2);
    glVertexAttribPointer(colorLoc2, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));

    glClearColor(0.0, 1.0, 1.0, 1.0);
}

//----------------------------------------------------------------------------
void display(void)
{
    // clear the window
    glClear(GL_COLOR_BUFFER_BIT);

    // Draw the points.  The parameters to the function are: the mode, the first
    // index, and the count.
    glBindVertexArray(vao[0]);
    glDrawArrays(GL_TRIANGLES, 0, NumPoints);
    glBindVertexArray(vao[1]);
    glDrawArrays(GL_LINES,0,NumPoints);
    glFlush();
    glutSwapBuffers();
}

//----------------------------------------------------------------------------
void keyboard(unsigned char key, int x, int y)
{
    switch (key) {

    // Quit when ESC is pressed
    case 27:
        exit(EXIT_SUCCESS);
        break;
    }
}

//---------------------------------------------------------------------------
int main(int argc, char** argv)
{
        vector< vector<float> > data = readDataSet(argv[1]);
        data = transposeData(data);
        vector< vector<int> > discreteData = discretizeData(data,20);
        vec3 * coolers = discreteToRGB(discreteData,data[0].size(),data.size());
        vec2 * vertices = makeVertices(data[0].size(),data.size());

        vector<vec2> contourVertices = makeContourVertices(discreteData);
        numLines = contourVertices.size();
        vec2 * contourVerts = new vec2[numLines];
        for(int i = 0; i < contourVertices.size(); i++)
        {
        contourVerts[i] = contourVertices[i];
        }

        vec3 * contourCoolers = makeContourColors(numLines);

        glutInit(&argc,argv);
        glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
        glutInitContextVersion(3,2);
        glutInitContextFlags(GLUT_FORWARD_COMPATIBLE);
        glutInitWindowSize(512,512);
        glutInitWindowPosition(500,300);
        glutCreateWindow("Dad's Like Visualization");
        glewExperimental = GL_TRUE;
        glewInit();

        init(vertices,coolers,contourVerts,contourCoolers);

        glutKeyboardFunc(keyboard);
        glutDisplayFunc(display);
        glutMainLoop();

        return(0);
}
